/* **********************************************************
 * Copyright 2010 Dimitar Vangelovski
 *
 * This file is part of the Gens3 3D Engine.
 *
 * The Gens3 3D Engine is free software; you can redistribute it and/or
 * modify it under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation.
 *
 * The Gens3 3D Engine is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Gens3 3D Engine; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 * 02110-1301, USA
 * **********************************************************/


#include "App.h"
#include "LoaderASE.h"
#include "3DMath.h"
#include "Lighting.h"
#include "ImageGL.h"

///////////////////////////////////////////////////////////////////////////////////////////////////

// some engine parameters
// FIXME: this should be put in a config file!

const char *	appTitle        = "Gens3 v0.5.5 beta";
const char *	pWorldFile      = "maps/map00.ase";
const char *    logFile         = "gens3.log";
int             Xres            = 640;
int             Yres            = 480;
const float 	Fov             = 67.5f;
const float 	zNear           = 1.0f;
const float 	zFar            = 20000.0f;
const char  	zBufDepth       = 24;
const char  	StencilBufDepth	= 0;
bool	        bFullScreen     = false;

///////////////////////////////////////////////////////////////////////////////////////////////////

FILE * CApp::m_logFile = NULL;

CApp::CApp()
{
    m_pSurfDisplay = NULL;
	m_bRunning = true;
	m_pKeys = NULL;
	m_fFrameInterval = 0.0f;
}

void CApp::WriteToLog(const char * pString)
{
    if (m_logFile != NULL)
    {
        fprintf(m_logFile, "%s", pString);
        fflush(m_logFile);
    }
}

bool CApp::OnInit()
{
    //WriteToLog(" ***** Gens3 3D Engine v0.5 beta ***** \n");
    WriteToLog(appTitle);
    WriteToLog("\nCopyright 2011, Dimitar Vangelovski\n");
    WriteToLog("\nInitializing engine...\n");

	if (SDL_Init(SDL_INIT_EVERYTHING) < 0)
	{
        WriteToLog("SDL failed to initialize!!\n");
		return false;
	}

	// detect what display modes are supported for fullscreen hardware rendering - the first mode is the largest resolution supported
	m_ppDisplayModes = SDL_ListModes(NULL, SDL_FULLSCREEN | SDL_HWSURFACE);

    if (m_ppDisplayModes[0]->w < Xres || m_ppDisplayModes[0]->h < Yres) // if the max res is smaller than the default, use that res
    {
        if (!CreateWindowGL(m_ppDisplayModes[0]->w, m_ppDisplayModes[0]->h))
        {
            WriteToLog("Failed to create OpenGL window!!\n");
            return false;
        }

        Xres = m_ppDisplayModes[0]->w;
        Yres = m_ppDisplayModes[0]->h;
    }
    else
    {
        if (!CreateWindowGL(Xres, Yres))
        {
            WriteToLog("Failed to create OpenGL window!!\n");
            return false;
        }
    }

    WriteToLog("Loading 3D world...\n");

	// Load the 3D world data
	CLoaderASE loader;
	loader.OpenFile(pWorldFile);
	loader.ParseFile();
	loader.CloseFile();

	m_pScene = loader.GetScene();

    WriteToLog("Loading textures...\n");

	// Load all the textures used in the world
	CImageGL theImage;
	if (!theImage.LoadGLTextures(m_pScene))
	{
        WriteToLog("Failed to load textures!!\n");
		return false;
	}

    WriteToLog("Initializing OpenGL...\n");

	// Initialize OpenGL
	if (!m_DrawGL.InitializeGL(m_pScene))
	{
        WriteToLog("Failed to initialize OpenGL!!\n");
		return false;
	}

    WriteToLog("Final setup...\n");

	m_Camera.SetPositionVector(10.0f, 0.0f, 10.0f);
	m_Camera.SetViewVector(0.0f, 0.0f, 10.0f);
	m_Camera.SetUpVector(0, 1, 0);
	m_Camera.Update(m_pSurfDisplay, m_pKeys, m_fFrameInterval);

	SDL_WM_SetCaption(appTitle, NULL);

	// disable the mouse cursor
	SDL_ShowCursor(SDL_DISABLE);

	return true;
}

void CApp::OnEvent(SDL_Event* pEvent)
{
	switch (pEvent->type)
	{
        case SDL_QUIT:
		{
			m_bRunning = false;
			break;
		}
        case SDL_VIDEORESIZE:
		{
			ReshapeGL(pEvent->resize.w, pEvent->resize.h);
			break;
		}
        case SDL_KEYDOWN:
		{
			m_pKeys = SDL_GetKeyState(NULL);

			if (m_pKeys[SDLK_ESCAPE]) m_bRunning = false;

			//if (m_pKeys[SDLK_F12]) SDL_WM_ToggleFullScreen(m_pSurfDisplay);

			if (m_pKeys[SDLK_F12])	// toggle fullscreen mode
			{
				if (bFullScreen)
				{
					bFullScreen = false;
					SDL_FreeSurface(m_pSurfDisplay);
					CreateWindowGL(Xres, Yres);
				}
				else
				{
					bFullScreen = true;
					SDL_FreeSurface(m_pSurfDisplay);
					CreateWindowGL(m_ppDisplayModes[0]->w, m_ppDisplayModes[0]->h);	// attempt to use the highest res available
					SDL_WM_ToggleFullScreen(m_pSurfDisplay);
				}
			}
			break;
		}
	}
}

// Perspective and viewport calculation
void CApp::ReshapeGL(int width, int height)
{
	glViewport(0, 0, (GLsizei)(width), (GLsizei)(height));

	// perspective calculation
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(Fov, (GLfloat)(width) / (GLfloat)(height), zNear, zFar);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}

// Create the OpenGL window
bool CApp::CreateWindowGL(int width, int height)
{
	SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 5);
	SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 16);
	SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

	//SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);
	//SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
	//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
	//SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 2);

	//if ((m_pSurfDisplay = SDL_SetVideoMode(width, height, 32, SDL_HWSURFACE | SDL_DOUBLEBUF)) == NULL)
	if ((m_pSurfDisplay = SDL_SetVideoMode(width, height, 32, SDL_HWSURFACE | SDL_OPENGLBLIT)) == NULL)
	{
        WriteToLog("Error setting display mode!!\n");
		return false;
	}

	// open the Screen Alpha Channel
	SDL_FillRect(m_pSurfDisplay, NULL, SDL_MapRGBA(m_pSurfDisplay->format, 0, 0, 0, 0));

	ReshapeGL(width, height);

	return true;
}

void CApp::CalculateFrameRate()
{
	static float framesPerSecond	= 0.0f;
	static float lastTime	        = 0.0f;
	static char strFrameRate[50]	= {0};

	static float frameTime = SDL_GetTicks() * 0.001f;

	float currentTime = SDL_GetTicks() * 0.001f;

 	m_fFrameInterval = currentTime - frameTime;
	frameTime = currentTime;

	framesPerSecond++;

	// after 1 second has passed => calculate the FPS
	if (currentTime - lastTime > 1.0f)
    {
		lastTime = currentTime;

		sprintf(strFrameRate, "   FPS: %d", int(framesPerSecond));

		std::string strTitle(appTitle);
		std::string strFPS(strFrameRate);
		strTitle += strFPS;

		//::SetWindowText(window.hWnd, NULL /*s2ws(strTitle).c_str()*/);
		SDL_WM_SetCaption(strTitle.c_str(), NULL);

		framesPerSecond = 0.0f;
    }
}

void CApp::OnRender()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glLoadIdentity();

	// update the camera view and position (move according mouse and keyboard events)
	m_Camera.Update(m_pSurfDisplay, m_pKeys, m_fFrameInterval);

	// update animated objects
	m_DrawGL.AnimateObjects(m_pScene);

	// check for camera (player) collision against the world
	m_Camera.CheckCameraCollision(m_pScene, m_fFrameInterval);

	// update world camera (player) position and direction
	m_Camera.Look();

	// Update the position coordinates of all the lights
	UpdateLights(m_pScene);

	//////////////////////////////////////////////////////////////////////////////////////////////
	// Draw all non transparent polygons first

	m_DrawGL.DrawNonTransparentSurfaces(m_pScene);

	/////////////////////////////////////////////////////////////////////////////////////////////
	// Draw all transparent polygons sorted from back to front

	m_DrawGL.DrawTransparentSurfaces(m_pScene);

	glFlush();

	SDL_GL_SwapBuffers();
}

int CApp::OnExecute()
{
    m_logFile = fopen(logFile, "w");    // create a log file

	if (OnInit() == false)
	{
		return -1;
	}

	SDL_Event Event;

	Uint32 tickCount;

	while (m_bRunning)
	{
		while (SDL_PollEvent(&Event))
		{
			OnEvent(&Event);
		}

		tickCount = SDL_GetTicks();

		CalculateFrameRate();
		OnRender();

		// Lock the Frame Rate to aproximately 40 FPS (this is a hack to solve some timing issues, when framerate is very high)
		while (SDL_GetTicks() - tickCount < 30) {};
	}

	OnCleanup();

	return 0;
}

void CApp::OnCleanup()
{
    m_DrawGL.DeinitializeGL();
	delete m_pScene;

    fclose(m_logFile);

	SDL_FreeSurface(m_pSurfDisplay);
	SDL_Quit();
}

int main(int argc, char* argv[])
{
	CApp theApp;

	return theApp.OnExecute();
}
